/*!
    \copyright  Copyright (c) 2021-2024 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    call_control_client
    \brief      Call control Profile - Client Role Implementation.
*/

#include "call_control_client.h"
#include "call_control_client_private.h"
#include "gatt_service_discovery.h"
#include "bt_device.h"
#include "pairing.h"
#include "device_list.h"
#include <focus_voice_source.h>
#include <focus_generic_source.h>
#include "event_bus_publish.h"
#include "le_audio_profile_channel_event_spec.h"

#ifdef INCLUDE_LE_AUDIO_UNICAST
/* Move functions to KEEP section such that they are not removed during garbage collection */
#define CALL_CTRL_CLIENT_KEEP UNITCODESECTION(KEEP)
#else
#define CALL_CTRL_CLIENT_KEEP
#endif

#define CCP_LOG          DEBUG_LOG
#define CCP_LOG_INFO     DEBUG_LOG_INFO

#define CCP_CHARACTERISTICS_CALL_STATE_LEN      (MAX_ACTIVE_CALLS_SUPPORTED * 3 * sizeof(uint8))

/*! Invalid handle for CCP */
#define CALL_CONTROL_CLIENT_INVALID_HANDLE (0xFFFF)

typedef struct
{
    CcpMessageId                    id;
    CcpProfileHandle                prflHndl;
    GattTelephoneBearerClientStatus status;
} call_control_client_read_cfm_hdr_t;

/*! \brief Call control client task data. */
call_control_client_task_data_t call_control_taskdata;

/*! \brief Handler that receives notification from Call Control Profile library */
static void CallControlClient_HandleMessage(Task task, MessageId id, Message message);

/*! \brief Get the stored handles from NVM. */
static void * callControlClient_RetrieveClientHandlesFromNvm(gatt_cid_t cid);

/*! \brief Callback for checking service rediscovery is needed or not */
static bool callControlClient_IsServiceRediscoveryNeeded(gatt_cid_t cid);

/*! \brief Action to take on the Call client instance */
typedef void (*call_control_action)(call_control_client_instance_t *instance, void *action_param);

/*! \brief Callbacks registered with gatt service discovery module */
static const gatt_service_discovery_callback_t call_control_client_gatt_service_discovery_cb =
{
    .IsServiceRediscoveryNeeded = callControlClient_IsServiceRediscoveryNeeded,
};

/*! \brief Publish Connection State Change */
static void callControlClient_PublishConnectionStatus(uint32 cid, int status)
{
    le_audio_profile_event_connection_state_change_data_t evt_data;

    evt_data.uuid = LE_AUDIO_TBS_UUID_SERVICE;
    evt_data.connection_handle = GattConnect_GetHandleFromConnectionId(cid);
    evt_data.status = status;
    EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_CCP_PROFILE, le_audio_profile_event_connection_state_change, &evt_data, sizeof(evt_data));
}

/*! \brief Publish Service Discovered */
static void callControlClient_PublishServiceDiscovered(uint32 cid)
{
    le_audio_profile_event_service_discovered_data_t evt_data;

    evt_data.handle = GattConnect_GetHandleFromConnectionId(cid);
    evt_data.status = 0;
    EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_CCP_PROFILE, le_audio_profile_event_service_discovered, &evt_data, sizeof(evt_data));
}

/*! \brief Publish Write Cfm */
static void callControlClient_PublishWriteCfm(uint32 cid, uint16 uuid, int status)
{
    le_audio_profile_event_char_write_rsp_data_t event_data;

    event_data.handle = GattConnect_GetHandleFromConnectionId(cid);
    event_data.uuid = uuid;
    event_data.status = status;
    EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_CCP_PROFILE, le_audio_profile_event_char_write_rsp, &event_data, sizeof(event_data));
}

/*! \brief Publish Read Error */
static void callControlClient_PublishReadError(Message message)
{
    call_control_client_read_cfm_hdr_t *read_cfm = (call_control_client_read_cfm_hdr_t *) message;
    call_control_client_instance_t *instance;
    uint16_t uuid = 0;

    switch(read_cfm->id)
    {
        case CCP_READ_PROVIDER_NAME_CFM:
            uuid = LE_AUDIO_TBS_UUID_BEARER_PROVIDER_NAME;
        break;

        case CCP_READ_BEARER_UCI_CFM:
            uuid = LE_AUDIO_TBS_UUID_BEARER_UCI;
        break;

        case CCP_READ_BEARER_TECHNOLOGY_CFM:
            uuid = LE_AUDIO_TBS_UUID_BEARER_TECHNOLOGY;
        break;

        case CCP_READ_BEARER_URI_SCHEMES_SUPPORTED_LIST_CFM:
            uuid = LE_AUDIO_TBS_UUID_BEARER_URI_PREFIX_LIST;
        break;

        case CCP_READ_SIGNAL_STRENGTH_CFM:
            uuid = LE_AUDIO_TBS_UUID_SIGNAL_STRENGTH;
        break;

        case CCP_READ_SIGNAL_STRENGTH_INTERVAL_CFM:
            uuid = LE_AUDIO_TBS_UUID_SIGNAL_STRENGTH_REPORTING_INTERVAL;
        break;

        case CCP_READ_CURRENT_CALLS_LIST_CFM:
            uuid = LE_AUDIO_TBS_UUID_LIST_CURRENT_CALLS;
        break;

        case CCP_READ_CONTENT_CONTROL_ID_CFM:
            uuid = LE_AUDIO_TBS_UUID_CALL_CONTROL_POINT;
        break;

        case CCP_READ_FEATURE_AND_STATUS_FLAGS_CFM:
            uuid = LE_AUDIO_TBS_UUID_STATUS_FLAGS;
        break;

        case CCP_READ_INCOMING_CALL_TARGET_BEARER_URI_CFM:
            uuid = LE_AUDIO_TBS_UUID_INCOMING_CALL_TARGET_BEARER_URI;
        break;

        case CCP_READ_CALL_STATE_CFM:
            uuid = LE_AUDIO_TBS_UUID_CALL_STATE;
        break;

        case CCP_READ_INCOMING_CALL_CFM:
            uuid = LE_AUDIO_TBS_UUID_INCOMING_CALL;
        break;

        case CCP_READ_CALL_FRIENDLY_NAME_CFM:
            uuid = LE_AUDIO_TBS_UUID_REMOTE_FRIENDLY_NAME;
        break;

        case CCP_READ_CCP_OPTIONAL_OPCODES_CFM:
            uuid = LE_AUDIO_TBS_UUID_CALL_CONTROL_POINT_OPCODES;
        break;

        default:
        break;
    }

    if (uuid == 0)
    {
        return;
    }

    instance = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned) read_cfm->prflHndl);
    if (read_cfm->status != GATT_TELEPHONE_BEARER_CLIENT_STATUS_SUCCESS && instance != NULL)
    {
        le_audio_profile_event_char_read_rsp_data_t event_data;

        event_data.handle = GattConnect_GetHandleFromConnectionId(instance->cid);
        event_data.uuid = uuid;
        event_data.status = (int) read_cfm->status;
        event_data.type = 1;
        event_data.len = 0;
        EventBus_Publish(EVENT_BUS_CHANNEL_LE_AUDIO_CCP_PROFILE, le_audio_profile_event_char_read_rsp, &event_data, sizeof(event_data));
    }
}

/*! \brief Callback function to handle GATT Connect notification */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_OnGattConnect(gatt_cid_t cid)
{
    UNUSED(cid);
}
CALL_CTRL_CLIENT_KEEP
/*! \brief Destroy call control profile if any established for this connection */
static void callControlClient_OnGattDisconnect(gatt_cid_t cid)
{
    call_control_client_instance_t *instance;

    CCP_LOG("callControlClient_OnGattDisconnect: cid=0x%04X", cid);
    instance = CallControlClient_GetInstance(call_client_compare_by_cid, (unsigned)cid);

    if (instance != NULL)
    {
        MessageCancelFirst(CallControlClient_GetTask(), CCP_INTERNAL_OUT_OF_BAND_RINGTONE_REQ);
        if (instance->state == call_client_state_discovery)
        {
            /* Initialization is in progress Do not place a destroy request. Just reset the instance */
            CallControlClient_ResetCallClientInstance(instance);
        }
        else
        {
            /* If remote_db_changed flag has been set, it means that destroy request has already been placed */
            if (!instance->remote_db_changed)
            {
                CcpDestroyReq(instance->ccp_profile_handle);
            }

            if (GattConnect_GetDisconnectReasonCode(instance->cid) != gatt_connect_disconnect_reason_link_transferred)
            {
                callControlClient_PublishConnectionStatus(instance->cid, -1);
            }
        }
    }
}

#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
/*! \brief Clear the stored client handles */
static void CallControlClient_ClearClienthandles(gatt_cid_t cid)
{
    device_t device = GattConnect_GetBtDevice(cid);

    if (device)
    {
        Device_RemoveProperty(device, device_property_call_control_client);
        DeviceDbSerialiser_SerialiseDevice(device);
    }
}

/*! \brief Handles remote database change indication */
static void CallControlClient_OnRemoteDbChange(gatt_cid_t cid)
{
    call_control_client_instance_t *instance;

    /* Clear the stored client handles */
    CallControlClient_ClearClienthandles(cid);

    instance = CallControlClient_GetInstance(call_client_compare_by_cid, (unsigned)cid);

    if (instance == NULL)
    {
        /* This means we disconnected already. So just acknowledge */
        GattConnect_AcknowledgeRemoteDbChange(cid);
    }
    else
    {
        /* If state is in 'call_client_state_discovery', do not deinit the profile here. Instead
           wait for the initialization to get completed. */
        if (instance->state != call_client_state_discovery)
        {
            /* De-init the profile */
            CcpDestroyReq(instance->ccp_profile_handle);
        }

        /* Disconnect is in progress. Wait for to complete before acknowledging */
        instance->remote_db_changed = TRUE;
    }
}
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

static const gatt_connect_observer_callback_t gatt_call_client_callback =
{
    .OnConnection = callControlClient_OnGattConnect,
    .OnDisconnection = callControlClient_OnGattDisconnect,
    .OnEncryptionChanged = NULL,
};

/*! \brief Function that checks whether the GTBS handles are already present in NVM */
CALL_CTRL_CLIENT_KEEP
static bool callControlClent_IsHandlesSameAsStoredData(GattTelephoneBearerClientDeviceData *current_data,
                                                       unsigned gatt_cid)
{
    GattTelephoneBearerClientDeviceData *stored_data = NULL;
    bool is_same_as_stored_data = FALSE;

    /* Get the GTBS handle information from NVM */
    stored_data = (GattTelephoneBearerClientDeviceData*) callControlClient_RetrieveClientHandlesFromNvm(gatt_cid);

    if (stored_data != NULL &&
        (memcmp(stored_data, current_data, sizeof(GattTelephoneBearerClientDeviceData)) == 0))
    {
        is_same_as_stored_data = TRUE;
    }

    return is_same_as_stored_data;
}

/*! \brief Function that checks whether the call client instance matches based on the compare type */
CALL_CTRL_CLIENT_KEEP
static bool callControlClient_Compare(call_instance_compare_by_type_t type,
                                      unsigned compare_value,
                                      call_control_client_instance_t *instance)
{
    bool found = FALSE;

    switch (type)
    {
        case call_client_compare_by_cid:
            found = instance->cid == (gatt_cid_t) compare_value;
        break;

        case call_client_compare_by_profile_handle:
            found = instance->ccp_profile_handle == (CcpProfileHandle) compare_value;
        break;

        case call_client_compare_by_state:
            found = instance->state == (call_client_state_t) compare_value;
        break;

        case call_client_compare_by_bdaddr:
        {
            bdaddr addr;
            bdaddr *device_addr = (bdaddr *) compare_value;
            found = instance->state == call_client_state_connected &&
                    GattConnect_GetPublicAddrFromConnectionId(instance->cid, &addr) &&
                    BdaddrIsSame(&addr, device_addr);
        }
        break;

        case call_client_compare_by_voice_source:
        {
            device_t device = GattConnect_GetBtDevice(instance->cid);

            if (device != NULL &&
                instance->state == call_client_state_connected &&
                DeviceProperties_GetLeVoiceSource(device) == (voice_source_t) compare_value)
            {
                found = TRUE;
            }
        }
        break;

        case call_client_compare_by_valid_invalid_cid :
            found = instance->state == call_client_state_connected &&
                   (instance->cid == (gatt_cid_t) compare_value || compare_value == INVALID_CID);

        default:
        break;
    }

    return found;
}

CALL_CTRL_CLIENT_KEEP
static pdd_size_t callControlClient_GetDeviceDataLength(device_t device)
{
    void *config = NULL;
    size_t config_size = 0;

    if (!Device_GetProperty(device, device_property_call_control_client, &config, &config_size))
    {
        config_size = 0;
    }
    return config_size;
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_SerialiseDeviceData(device_t device, void *buf, pdd_size_t offset)
{
    void *config = NULL;
    size_t config_size = 0;
    UNUSED(offset);

    if (Device_GetProperty(device, device_property_call_control_client, &config, &config_size))
    {
        memcpy(buf, config, config_size);
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_DeserialiseDeviceData(device_t device, void *buf, pdd_size_t data_length, pdd_size_t offset)
{
    UNUSED(offset);

    Device_SetProperty(device, device_property_call_control_client, buf, data_length);
}

CALL_CTRL_CLIENT_KEEP
void CallControlClient_RegisterAsPersistentDeviceDataUser(void)
{
    DeviceDbSerialiser_RegisterPersistentDeviceDataUser(
        PDDU_ID_LEA_CALL_CLIENT_CONTROL,
        callControlClient_GetDeviceDataLength,
        callControlClient_SerialiseDeviceData,
        callControlClient_DeserialiseDeviceData);
}

/*! \brief Write Call control client device data to NVM */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_WriteCallControlDeviceDataToStore(call_control_client_instance_t *instance)
{
    GattTelephoneBearerClientDeviceData *dev_data = NULL;

    /* Retrieve all the discovered GTBS service handles */
    dev_data = CcpGetTelephoneBearerAttributeHandles(instance->ccp_profile_handle, 0);

    /* Try to store the GTBS handle information in NVM */
    if (dev_data != NULL &&
        !callControlClent_IsHandlesSameAsStoredData(dev_data, instance->cid))
    {
        CCP_LOG("callControlClient_WriteCallControlDeviceDataToStore");
        CallControlClient_StoreClientHandles(instance->cid,
                                             (void*)dev_data,
                                             sizeof(GattTelephoneBearerClientDeviceData));
    }

    /* Free the GTBS handle information */
    if (dev_data != NULL)
    {
        pfree(dev_data);
    }
}

/*! \brief Upon a successful initialization of CCP, preserve the information in call client instance */
CALL_CTRL_CLIENT_KEEP
static void  callControlClient_HandleCallProfileInitConfirmation(const CcpInitCfm *message)
{
    call_control_client_instance_t *instance;

    instance = CallControlClient_GetInstance(call_client_compare_by_profile_handle, message->prflHndl);

    CCP_LOG("callControlClient_HandleCallProfileInitConfirmation prfl_handle: 0x%x, status: %d, instance: %p",
            message->prflHndl, message->status, instance);

    if (!instance)
    {
        /* Instance not found; find an unused slot */
        instance = CallControlClient_GetInstance(call_client_compare_by_profile_handle, 0);
        CCP_LOG("callControlClient_HandleCallProfileInitConfirmation: new instance %p", instance);
    }

    if (instance != NULL &&
        message->status == CCP_STATUS_SUCCESS &&
        instance->ccp_profile_handle == message->prflHndl &&
        !instance->remote_db_changed)
    {
        instance->state = call_client_state_connected;

        if (!instance->handover_in_progress)
        {
            CCP_LOG("callControlClient_HandleCallProfileInitConfirmation Configuring for notifications");
            CcpSetCallStateNotificationRequest(instance->ccp_profile_handle, TRUE);
            CcpSetTerminationReasonNotificationRequest(instance->ccp_profile_handle, TRUE);
            CcpSetCallControlPointNotificationRequest(instance->ccp_profile_handle, TRUE);
            CcpSetFlagsNotificationRequest(instance->ccp_profile_handle, TRUE);
            CcpReadContentControlIdRequest(instance->ccp_profile_handle);
            CcpReadStatusAndFeatureFlagsRequest(instance->ccp_profile_handle);
            CcpReadCallStateRequest(instance->ccp_profile_handle);
            CcpReadIncomingCallRequest(instance->ccp_profile_handle);
            CcpReadCallFriendlyNameRequest(instance->ccp_profile_handle);
            CcpReadCallControlPointOptionalOpcodesRequest(instance->ccp_profile_handle);

            callControlClient_PublishConnectionStatus(instance->cid, 0);
        }
        else
        {
            instance->handover_in_progress = FALSE;
        }

        callControlClient_WriteCallControlDeviceDataToStore(instance);
    }
    else if (message->status == CCP_STATUS_IN_PROGRESS &&
             instance != NULL &&
             instance->ccp_profile_handle == 0)
    {
        instance->ccp_profile_handle = message->prflHndl;
    }
    else
    {
        if (message->status == CCP_STATUS_SUCCESS &&
            (instance == NULL || instance->ccp_profile_handle != message->prflHndl || instance->remote_db_changed))
        {
            /* Profile initialization is successful, but there could be below conditions:
             * 1. Instance is NULL, which indicates the GATT connection has already gone.
             *                             (OR)
             * 2. Instance is not NULL, but profile handles mismatch.This could happen
             *    if the init cfm has arrived very late, while we are already processing
             *    a new init request.Though a remote possibility, it can happen
             *                             (OR)
             * 3. Remote database got changed while we initializing CCP.
             *
             *    Under all above conditions place a destroy request immediately.
             */
            CcpDestroyReq(message->prflHndl);
        }
        else
        {

#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
            /* Acknowledge if remote database changed while initialization */
            if (instance != NULL && instance->remote_db_changed)
            {
                GattConnect_AcknowledgeRemoteDbChange(instance->cid);
            }
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

            /* An error occurred during CCP internal discovery. Reset the Call client instance */
            CallControlClient_ResetCallClientInstance(instance);
        }
    }
}

/*! \brief Upon receiving a destroy confirmation, preserve the handles in NVM */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleCallProfileDestroyConfirmation(const CcpDestroyCfm *message)
{
    call_control_client_instance_t *call_client;

    /* Find the matching call client instance based on the profile handle */
    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)message->prflHndl);

    CCP_LOG("callControlClient_HandleCallProfileDestroyConfirmation prfl_handle: 0x%x, status: %d, instance: %p",
            message->prflHndl, message->status, call_client);

    if (call_client != NULL && message->status != CCP_STATUS_IN_PROGRESS)
    {
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
        /* Acknowledge if destroy is due to remote database change */
        if (call_client->remote_db_changed)
        {
            GattConnect_AcknowledgeRemoteDbChange(call_client->cid);
        }
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */

        /* Reset the media client instance */
        CallControlClient_ResetCallClientInstance(call_client);
    }
}

/*! \brief Handle control point write confirmation */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleCallControlPointWriteConfirmation(const CcpSetCfm *message)
{
    call_control_client_instance_t *call_client;

    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)message->prflHndl);

    CCP_LOG("callControlClient_HandleCallControlPointWriteConfirmation prfl_handle: 0x%x, srvc_handle: 0x%x, status: %d",
            message->prflHndl, message->srvcHndl, message->status);

    if (call_client != NULL)
    {
        callControlClient_PublishWriteCfm(call_client->cid, LE_AUDIO_TBS_UUID_CALL_CONTROL_POINT, message->status);
    }
}

static void callControlClient_HandleCallWriteSignalStrengthIntervalCfm(const CcpSetCfm *message)
{
    call_control_client_instance_t *call_client;

    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)message->prflHndl);

    CCP_LOG("callControlClient_HandleCallWriteSignalStrengthIntervalCfm prfl_handle: 0x%x, srvc_handle: 0x%x, status: %d",
            message->prflHndl, message->srvcHndl, message->status);

    if (call_client != NULL)
    {
        callControlClient_PublishWriteCfm(call_client->cid, LE_AUDIO_TBS_UUID_SIGNAL_STRENGTH_REPORTING_INTERVAL, message->status);
    }
}

CALL_CTRL_CLIENT_KEEP
static ccp_call_info_t* callControlClient_FindCallStateForTbsCallId(call_control_client_instance_t *instance, uint8 call_id)
{
    ccp_call_info_t *call_info = NULL;

    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        if (instance->call_state[idx].tbs_call_id == call_id)
        {
            call_info = &instance->call_state[idx];
        }
    }

    return call_info;
}

CALL_CTRL_CLIENT_KEEP
static ccp_call_info_t * callControlClient_AddCallState(call_control_client_instance_t *instance, TbsCallState *tbs_call_state)
{
    ccp_call_info_t *call_info = NULL;

    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        if (instance->call_state[idx].state == CCP_CALL_STATE_IDLE)
        {
           /*A free slot in call state array in*/
           call_info = &instance->call_state[idx];
           CcpSm_SetCallState(instance->cid, call_info, tbs_call_state);
           return call_info;
        }
    }

    CCP_LOG("callControlClient_AddCallState : No free slot available, Rejecting!");
    return call_info;
}

CALL_CTRL_CLIENT_KEEP
static bool callControlClient_IsAnyOtherVoiceSourceActive(voice_source_t this_source)
{
    bool result = FALSE;
    generic_source_t current_focused_source = Focus_GetFocusedGenericSourceForAudioRouting();

    if (current_focused_source.type == source_type_voice && current_focused_source.u.voice != this_source)
    {
        result = TRUE;
    }

    CCP_LOG("callControlClient_IsAnyOtherVoiceSourceActive %d", result);

    return result;
}

CALL_CTRL_CLIENT_KEEP
void CallControlClient_PlayOutOfBandRingtone(voice_source_t source)
{
    call_control_client_instance_t *instance;
    instance = CallControlClient_GetInstance(call_client_compare_by_voice_source, (unsigned)source);

    /* If the instance is NULL, it indicates CCP profile is disconnected already. Just return */
    if (instance == NULL)
    {
        return;
    }

    CCP_LOG("CallControlClient_PlayOutOfBandRingtone : in_band_ring : %d", instance->tbs_status_info.in_band_ring);

    /* Play ring tone if AG doesn't support in band ringing OR 
       In case of multipoint play out of band ringtone if there is an ongoing
       call for other source. Even If this current source tries to play in band ringtone,
       it will never get focus as context_voice_ringing_incoming is lower priority
       than context_voice_in_call and couldn't play in band ringtone.
    */
    if (!instance->tbs_status_info.in_band_ring || callControlClient_IsAnyOtherVoiceSourceActive(source))
    {
        for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
        {
            ccp_call_state_t call_state = CcpSm_GetCallState(instance, idx);
            if (call_state == CCP_CALL_STATE_INCOMING)
            {
                Telephony_NotifyCallIncomingOutOfBandRingtone(source);
                MESSAGE_MAKE(message, CCP_INTERNAL_OUT_OF_BAND_RINGTONE_REQ_T);
                message->source = source;
                MessageCancelFirst(CallControlClient_GetTask(), CCP_INTERNAL_OUT_OF_BAND_RINGTONE_REQ);
                MessageSendLater(CallControlClient_GetTask(), CCP_INTERNAL_OUT_OF_BAND_RINGTONE_REQ, message, D_SEC(5));
                return;
            }
        }
    }
    else
    {
        /* @Todo Revisit to play in band ringtone*/
    }
}

CALL_CTRL_CLIENT_KEEP
voice_source_t CallControlClient_GetVoiceSourceForCid(gatt_cid_t cid)
{
    voice_source_t source = voice_source_none;
    bdaddr bd_addr;

    GattConnect_GetPublicAddrFromConnectionId(cid, &bd_addr);
    device_t device = BtDevice_GetDeviceForBdAddr(&bd_addr);

    if (device)
    {
        source = DeviceProperties_GetLeVoiceSource(device);
    }

    return source;
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_UpdateCallState(CcpProfileHandle ccp_profile_handle, TbsCallState *call_state_list, uint8 call_state_list_size)
{
    call_control_client_instance_t *call_client;
    TbsCallState *tbs_call_state = NULL;
    TbsCallState tbs_call_state_clear;
    ccp_call_info_t *call_info = NULL;

    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)ccp_profile_handle);
    PanicNull(call_client);

    CCP_LOG("callControlClient_UpdateCallState prfl_handle: 0x%x, callStateListSize: %d, instance: %p",
            ccp_profile_handle, call_state_list_size, call_client);

    /* Empty call list means there is no call at the remote, clear the call states */
    if (call_state_list_size == 0)
    {
        CCP_LOG("callControlClient_UpdateCallState Empty call list, clearing the call states");
        for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
        {
            call_info = &call_client->call_state[idx];
            tbs_call_state_clear.callId = call_info->tbs_call_id;
            tbs_call_state_clear.callState = TBS_CALL_STATE_INVALID;
            /* Clear the call state*/
            CcpSm_SetCallState(call_client->cid, call_info, &tbs_call_state_clear);
        }
        return;
    }

    for (uint8 idx = 0; idx < call_state_list_size; idx++)
    {
        tbs_call_state = &call_state_list[idx];

        CCP_LOG("callControlClient_UpdateCallState callId: 0x%02x, callState: 0x%02x, callFlag: 0x%02x,",
                    tbs_call_state->callId, tbs_call_state->callState, tbs_call_state->callFlags);
        if (tbs_call_state->callId == 0 || tbs_call_state->callState > TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD)
        {
            continue;
        }

        call_info = callControlClient_FindCallStateForTbsCallId(call_client, tbs_call_state->callId);

        if (call_info != NULL)
        {
            /* Handle the call state from the call state list */
            CcpSm_SetCallState(call_client->cid, call_info, tbs_call_state);
        }
        else
        {
            CCP_LOG("callControlClient_UpdateCallState New call. Adding to the call state instance");
            call_info = callControlClient_AddCallState(call_client, tbs_call_state);
        }

        if (call_info != NULL && call_info->state == CCP_CALL_STATE_INCOMING)
        {
            /* Req to play an out-of-band ring once to notify user */
            CallControlClient_PlayOutOfBandRingtone(CallControlClient_GetVoiceSourceForCid(call_client->cid));
        }
     }
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleFlagsInd(const CcpFlagsInd *message)
{
    call_control_client_instance_t *call_client;
    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)message->prflHndl);
    PanicNull(call_client);

    call_client->tbs_status_info.in_band_ring = IsInbandRingingEnabled(message->flags);
    call_client->tbs_status_info.silent_mode = IsServerInSilentMode(message->flags);

    CCP_LOG("callControlClient_HandleFlagsInd in_band_ring :%d, silent_mode :%d", call_client->tbs_status_info.in_band_ring,
             call_client->tbs_status_info.silent_mode);
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleCallTerminationReasonNotification(const CcpTerminationReasonInd *message)
{
    call_control_client_instance_t *call_client;
    TbsCallState tbs_call_state;
    ccp_call_info_t *call_info = NULL;
    uint8 reason_code;

    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)message->prflHndl);
    PanicNull(call_client);

    reason_code = message->reasonCode;

    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        if (message->callId == call_client->call_state[idx].tbs_call_id)
        {
            call_info = &call_client->call_state[idx];
            tbs_call_state.callId = call_info->tbs_call_id;
            tbs_call_state.callState = TBS_CALL_STATE_INVALID;

            /* For detailed call termination reason check Enumeration of call termination reasons GattTbsCallTerminationReason */
            CCP_LOG("callControlClient_HandleCallTerminationReasonNotification callId = %d, active_call_index = %d, reasonCode=0x%x",
                    message->callId, call_client->call_state[idx].tbs_call_id, reason_code);
            /* Clear the call state*/
            CcpSm_SetCallState(call_client->cid, call_info, &tbs_call_state);
            break;
        }
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleReadContentControlIdCfm(const CcpReadContentControlIdCfm *message)
{
    call_control_client_instance_t *call_client;

    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)message->prflHndl);
    PanicNull(call_client);

    if (message->status == CCP_STATUS_SUCCESS)
    {
        call_client->content_control_id = message->contentControlId;
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleReadFeatureAndStatusFlagsCfm(const CcpReadFlagsCfm *message)
{
    call_control_client_instance_t *call_client;

    call_client = CallControlClient_GetInstance(call_client_compare_by_profile_handle, (unsigned)message->prflHndl);
    PanicNull(call_client);
    call_client->tbs_status_info.in_band_ring = IsInbandRingingEnabled(message->flags);
    call_client->tbs_status_info.silent_mode = IsServerInSilentMode(message->flags);
}

/*! \brief Process notifications received from CCP library */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleCcpMessage(Message message)
{
    CsrBtCmPrim ccp_id = *(CsrBtCmPrim *)message;
    const CcpReadCallStateCfm *msg_cfm = NULL;
    const CcpCallStateInd *msg_ind = NULL;

    CCP_LOG_INFO("callControlClient_HandleCcpMessage Received Message Id : 0x%x", (ccp_id - CCP_MESSAGE_BASE));

    switch (ccp_id)
    {
        case CCP_INIT_CFM:
            callControlClient_HandleCallProfileInitConfirmation((const CcpInitCfm*)message);
        break;

        case CCP_DESTROY_CFM:
            callControlClient_HandleCallProfileDestroyConfirmation((const CcpDestroyCfm*)message);
        break;

        case CCP_READ_CONTENT_CONTROL_ID_CFM:
            callControlClient_HandleReadContentControlIdCfm((const CcpReadContentControlIdCfm*)message);
        break;

        case CCP_READ_FEATURE_AND_STATUS_FLAGS_CFM:
            callControlClient_HandleReadFeatureAndStatusFlagsCfm((const CcpReadFlagsCfm*)message);
        break;

        case CCP_WRITE_CALL_CONTROL_POINT_CFM:
            callControlClient_HandleCallControlPointWriteConfirmation((const CcpSetCfm*)message);
        break;

        case CCP_READ_CALL_STATE_CFM:
            msg_cfm = (const CcpReadCallStateCfm*)message;
            if (msg_cfm->status == GATT_TELEPHONE_BEARER_CLIENT_STATUS_SUCCESS)
            {
                callControlClient_UpdateCallState(msg_cfm->prflHndl, (TbsCallState *)msg_cfm->callStateList, msg_cfm->callStateListSize);
            }
        break;

        case CCP_CALL_STATE_IND:
            msg_ind = (const CcpCallStateInd*)message;
            callControlClient_UpdateCallState(msg_ind->prflHndl, (TbsCallState *)msg_ind->callStateList, msg_ind->callStateListSize);
        break;

        case CCP_TERMINATION_REASON_IND:
            callControlClient_HandleCallTerminationReasonNotification((const CcpTerminationReasonInd*)message);
        break;

        case CCP_FLAGS_IND:
            callControlClient_HandleFlagsInd((const CcpFlagsInd*)message);
        break;
        case CCP_WRITE_SIGNAL_STRENGTH_INTERVAL_CFM:
            callControlClient_HandleCallWriteSignalStrengthIntervalCfm((const CcpSetCfm *) message);
        break;

        default:
            CCP_LOG("callControlClient_HandleCcpMessage Unhandled message id: 0x%x", (*(CsrBtCmPrim *)message));
        break;
    }

    callControlClient_PublishReadError(message);
}

/*! \brief Create the Call Control Client Instance */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_CreateInstance(gatt_cid_t cid)
{
    call_control_client_instance_t *instance = NULL;
    CcpHandles ccp_handle_data;
    CcpInitData client_init_params;
    GattTelephoneBearerClientDeviceData *tbs_client_handle = NULL;

    memset(&ccp_handle_data, 0, sizeof(CcpHandles));
    instance = CallControlClient_GetInstance(call_client_compare_by_state, call_client_state_idle);

    if (instance != NULL)
    {
        instance->cid = cid;
        instance->state = call_client_state_discovery;

        tbs_client_handle = (GattTelephoneBearerClientDeviceData*)CallControlClient_RetrieveClientHandles(cid);
        if (tbs_client_handle)
        {
            memcpy(&ccp_handle_data.tbsHandle, (void*)tbs_client_handle, sizeof(GattTelephoneBearerClientDeviceData));
        }

        client_init_params.cid = cid;
        CcpInitReq(TrapToOxygenTask((Task)&call_control_taskdata.task_data),
                    &client_init_params,
                    tbs_client_handle == NULL ? NULL : &ccp_handle_data,
                    FALSE);
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlClient_WriteInvalidClientHandles(gatt_cid_t cid)
{
    GattTelephoneBearerClientDeviceData dev_data = {.startHandle = CALL_CONTROL_CLIENT_INVALID_HANDLE,
                                                    .endHandle = CALL_CONTROL_CLIENT_INVALID_HANDLE};

    /* Store invalid GTBS handle information in NVM if not already stored */
    if (!callControlClent_IsHandlesSameAsStoredData(&dev_data, cid))
    {
        CCP_LOG("callControlClient_WriteInvalidClientHandles cid 0x%x", cid);
        CallControlClient_StoreClientHandles(cid,
                                             (void*)&dev_data,
                                             sizeof(GattTelephoneBearerClientDeviceData));
    }
}

/*! \brief If GTBS Service is discovered successfully, initialize the Call control profile */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleServiceRange(const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T *message)
{
    bool gtbs_service_present = FALSE;

    CCP_LOG("callControlClient_HandleServiceRange Number of LEA Services cid: 0x%x, result: %d, Found: %d",
            message->cid, message->result, message->srvcInfoCount);

    if (message->result == GATT_SD_RESULT_SUCCESS && message->srvcInfoCount != 0)
    {
        if (message->srvcInfoCount == 1 && (message->srvcInfo->srvcId & GATT_SD_TBS_SRVC))
        {
            callControlClient_CreateInstance(message->cid);

            callControlClient_PublishServiceDiscovered(message->cid);

            gtbs_service_present = TRUE;
        }
        else
        {
            CCP_LOG("callControlClient_HandleServiceRange unique GTBS Service not found in Remote Server CID: %d", message->cid);
        }

        pfree(message->srvcInfo);
    }


    if (!gtbs_service_present &&
        (message->result == GATT_SD_RESULT_SUCCESS || message->result == GATT_SD_RESULT_SRVC_ID_NOT_FOUND))
    {
        /* GTBS is not present in remote server. Write invalid handles to NVM so that we don't trigger unnecessary service
           rediscovery on callControlClient_IsServiceRediscoveryNeeded(). */
        callControlClient_WriteInvalidClientHandles(message->cid);
    }
}

/*! \brief Handler to handle GATT Service Discovery related primitives */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandleGattPrim(Message message)
{
    switch (*(CsrBtCmPrim *)message)
    {
        case GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM:
            callControlClient_HandleServiceRange((const GATT_SERVICE_DISCOVERY_FIND_SERVICE_RANGE_CFM_T*)message);
        break;

        default:
        break;
    }
}

/*! \brief Check if the Call control service is available in remote server and is discovered */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_SetServiceStatusForCid(gatt_cid_t cid, bool service_not_found)
{
    CCP_LOG("CallControlClient_SetServiceStatusForCid cid: 0x%x service_not_found %d", cid, service_not_found);

    if (!service_not_found)
    {
        if (CallControlClient_RetrieveClientHandles(cid) != NULL)
        {
            CCP_LOG("CallControlClient_SetServiceStatusForCid create instance");
            /* If handles are already there, it means we can directly create the instance */
            callControlClient_CreateInstance(cid);
            callControlClient_PublishServiceDiscovered(cid);
        }
        else
        {
            /* Client handles not exists, find the service range first before creating instance */
            CCP_LOG("CallControlClient_SetServiceStatusForCid start find service range");
            GattServiceDiscoveryFindServiceRange(TrapToOxygenTask((Task)&call_control_taskdata.task_data),
                                                 cid,
                                                 GATT_SD_TBS_SRVC);
        }
    }
    else
    {
        /* GTBS is not present in remote server. Write invalid handles to NVM so that we don't trigger unnecessary service
           rediscovery on callControlClient_IsServiceRediscoveryNeeded(). */
        callControlClient_WriteInvalidClientHandles(cid);
    }
}

/*! \brief Executes the assigned action on the connected call profile */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_ActionOnConnectedProfile(gatt_cid_t cid, call_control_action action_fn, void *action_param)
{
    call_control_client_instance_t *instance = NULL;

    for (instance = &call_control_taskdata.call_client_instance[0];
         instance < &call_control_taskdata.call_client_instance[MAX_CALL_SERVER_SUPPORTED];
         instance++)
    {
        if (instance->state == call_client_state_connected &&
            (cid == INVALID_CID || cid == instance->cid))
        {
            action_fn(instance, action_param);

            if (instance->cid == cid)
            {
                break;
            }
        }
    }
}

/*! \brief When pairing completes with handset, register for call state notifications */
CALL_CTRL_CLIENT_KEEP
static void callControlClient_HandlePairingActivity(const PAIRING_ACTIVITY_T *message)
{
    call_control_client_instance_t *instance = NULL;

    if (message->status != pairingActivitySuccess)
    {
        return;
    }

    instance = CallControlClient_GetInstance(call_client_compare_by_bdaddr, (unsigned)&message->device_addr);

    if (instance != NULL)
    {
        CCP_LOG("callControlClient_HandlePairingActivity Registering for CID 0x%x", instance->cid);
        CcpSetCallStateNotificationRequest(instance->ccp_profile_handle, TRUE);
        CcpSetTerminationReasonNotificationRequest(instance->ccp_profile_handle, TRUE);
        CcpSetCallControlPointNotificationRequest(instance->ccp_profile_handle, TRUE);
        CcpSetFlagsNotificationRequest(instance->ccp_profile_handle, TRUE);
        CcpReadContentControlIdRequest(instance->ccp_profile_handle);
        CcpReadStatusAndFeatureFlagsRequest(instance->ccp_profile_handle);
        CcpReadCallStateRequest(instance->ccp_profile_handle);
        CcpReadIncomingCallRequest(instance->ccp_profile_handle);
        CcpReadCallFriendlyNameRequest(instance->ccp_profile_handle);
        CcpReadCallControlPointOptionalOpcodesRequest(instance->ccp_profile_handle);

        callControlClient_WriteCallControlDeviceDataToStore(instance);
    }
}

/*! \brief Process notifications received for Call control client task */
CALL_CTRL_CLIENT_KEEP
static void CallControlClient_HandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    CCP_LOG("CallControlClient_HandleMessage Received Message Id : 0x%x", id);

    switch (id)
    {
        case CCP_PROFILE_PRIM:
            callControlClient_HandleCcpMessage(message);
        break;

        case GATT_SD_PRIM:
            callControlClient_HandleGattPrim(message);
        break;

        case PAIRING_ACTIVITY:
            callControlClient_HandlePairingActivity((const PAIRING_ACTIVITY_T*)message);
        break;

        case CCP_INTERNAL_OUT_OF_BAND_RINGTONE_REQ:
            CallControlClient_PlayOutOfBandRingtone(((CCP_INTERNAL_OUT_OF_BAND_RINGTONE_REQ_T *)message)->source);
        break;

        default:
        break;
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlPoint_AcceptIncomingCall(call_control_client_instance_t *instance, call_control_set_t *set_value)
{
    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        if (CcpSm_GetCallState(instance, idx) == CCP_CALL_STATE_INCOMING)
        {
            CCP_LOG("callControlPoint_AcceptIncomingCall callId: %d", instance->call_state[idx].tbs_call_id);
            CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, set_value->op,
                instance->call_state[idx].tbs_call_id);
            break;
        }
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlPoint_TerminateCall(call_control_client_instance_t *instance, call_control_set_t *set_value)
{
    CCP_LOG("callControlPoint_TerminateCall callId: 0x%x ", set_value->val);
    if (set_value->val != 0)
    {
        CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle,
                                              set_value->op,
                                              set_value->val);
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlPoint_LocalHold(call_control_client_instance_t *instance, call_control_set_t *set_value)
{
    ccp_call_state_t call_state = CcpSm_GetCallStateForMatchingCallId(instance, set_value->val);

    CCP_LOG("callControlPoint_LocalHold callId: 0x%x callState:  enum:ccp_call_state_t:%d ", set_value->val, call_state);
    if (call_state == CCP_CALL_STATE_INCOMING ||
        call_state == CCP_CALL_STATE_ACTIVE ||
        call_state == CCP_CALL_STATE_REMOTELY_HELD)
    {
        CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, set_value->op, set_value->val);
        CcpSm_SetPendingCommandForMatchingCallId(instance, set_value->val, set_value->op);
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlPoint_LocalRetrieve(call_control_client_instance_t *instance, call_control_set_t *set_value)
{
    ccp_call_state_t call_state = CcpSm_GetCallStateForMatchingCallId(instance, set_value->val);

    CCP_LOG("callControlPoint_LocalRetrieve callId: 0x%x callState: enum:ccp_call_state_t:%d ", set_value->val, call_state);
    if (call_state == CCP_CALL_STATE_LOCALLY_HELD ||
        call_state == CCP_CALL_STATE_LOCALLY_REMOTELY_HELD)
    {
        CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, set_value->op, set_value->val);
    }
}

CALL_CTRL_CLIENT_KEEP
static void callControlPoint_OriginateCall(call_control_client_instance_t *instance, call_control_set_t *set_value)
{
    UNUSED(instance);
    UNUSED(set_value);
}

CALL_CTRL_CLIENT_KEEP
static void callControlPoint_JoinCall(call_control_client_instance_t *instance, call_control_set_t *set_value)
{
    uint8 tbs_call_index[MAX_ACTIVE_CALLS_SUPPORTED] = {0};
    uint8 count = 0;

    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        ccp_call_state_t call_state = CcpSm_GetCallState(instance, idx);

        if (call_state == CCP_CALL_STATE_ACTIVE ||
            call_state == CCP_CALL_STATE_LOCALLY_HELD ||
            call_state == CCP_CALL_STATE_REMOTELY_HELD ||
            call_state == CCP_CALL_STATE_LOCALLY_REMOTELY_HELD)
        {
            tbs_call_index[count++] = instance->call_state[idx].tbs_call_id;
        }
    }

    if (count > 1)
    {
        uint8 size = count;
        CcpWriteCallControlPointRequest(instance->ccp_profile_handle, set_value->op, size, tbs_call_index);
    }
}

/*! \brief Action function that sends the opcode to the remote Server */
CALL_CTRL_CLIENT_KEEP
static void callControlPoint_SetOpcode(call_control_client_instance_t *instance, void *action_param)
{
    call_control_set_t *set_value = (call_control_set_t *) action_param;

    CCP_LOG_INFO("callControlPoint_SetOpcode Opcode:0x%x cid: 0x%x", set_value->op, instance->cid);
    /* Set control point interface.*/
    switch (set_value->op)
    {
        case TBS_CCP_ACCEPT:
            callControlPoint_AcceptIncomingCall(instance, set_value);
        break;

        case TBS_CCP_TERMINATE :
            callControlPoint_TerminateCall(instance, set_value);
        break;

        case TBS_CCP_LOCAL_HOLD :
            callControlPoint_LocalHold(instance, set_value);
        break;

        case TBS_CCP_LOCAL_RETRIEVE :
            callControlPoint_LocalRetrieve(instance, set_value);
        break;

        case TBS_CCP_ORIGINATE :
            callControlPoint_OriginateCall(instance, set_value);
        break;

        case TBS_CCP_JOIN :
            callControlPoint_JoinCall(instance, set_value);
        break;

        default :
            break;
    }
}

/*! \brief Callback for checking service rediscovery is needed or not */
CALL_CTRL_CLIENT_KEEP
static bool callControlClient_IsServiceRediscoveryNeeded(gatt_cid_t cid)
{
    /* If there is no client handles in NVM, then that is an indication that GTBS service discovery
       is not yet initiated. So request for rediscovery in that case.
       Note that if GTBS is not in remote server, then the client handles will not be NULL (it will be
       invalid though) and we will not request for re-discovery. */
   return callControlClient_RetrieveClientHandlesFromNvm(cid) == NULL;
}

/*! \brief Register with GATT LEA Service discovery */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_Init(void)
{
    CCP_LOG("CallControlClient_Init");
    memset(&call_control_taskdata, 0, sizeof(call_control_taskdata));
    call_control_taskdata.task_data.handler = CallControlClient_HandleMessage;

    for (int idx = 0; idx < MAX_CALL_SERVER_SUPPORTED; idx++)
    {
        CallControlClient_ResetCallClientInstance(&call_control_taskdata.call_client_instance[idx]);
    }

    GattConnect_RegisterObserver(&gatt_call_client_callback);
    Pairing_ActivityClientRegister(&call_control_taskdata.task_data);
    GattServiceDiscovery_RegisterServiceForDiscovery(GATT_SD_TBS_SRVC);
    GattServiceDiscovery_RegisterForCallback(&call_control_client_gatt_service_discovery_cb);
#ifdef ENABLE_REMOTE_DB_CHANGE_HANDLING
    GattConnect_RegisterRemoteDbChangeCallback(CallControlClient_OnRemoteDbChange);
#endif /* ENABLE_REMOTE_DB_CHANGE_HANDLING */
}

/*! \brief Send the call control opcode to the remote server */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_SendCallControlOpcode(gatt_cid_t cid, GattTbsOpcode op, int32 val)
{
    call_control_set_t set_value;

    set_value.op = op;
    set_value.val = val;

    CCP_LOG("CallControlClient_SendCallControlOpcode OPCODE:0x%x, callId:%d", op, val);
    callControlClient_ActionOnConnectedProfile(cid, callControlPoint_SetOpcode, &set_value);
}

CALL_CTRL_CLIENT_KEEP
void CallControlClient_TerminateCalls(voice_source_t source, call_terminate_type_t terminate_type)
{
    call_control_set_t set_value;
    call_control_client_instance_t *instance = NULL;

    instance = CallControlClient_GetInstance(call_client_compare_by_voice_source, (unsigned) source);

    if (instance == NULL)
    {
        return;
    }

    set_value.op = TBS_CCP_TERMINATE;

    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        ccp_call_state_t call_state = CcpSm_GetCallState(instance, idx);

        if (terminate_type == CCP_TERMINATE_ALL_CALLS && call_state != CCP_CALL_STATE_IDLE)
        {
            set_value.val = instance->call_state[idx].tbs_call_id;
        }
        else if (terminate_type == CCP_HANGUP_ONGOING_CALLS &&
                 CallControlClient_IsCallOngoing(call_state))
        {
            set_value.val = instance->call_state[idx].tbs_call_id;
        }
        else if (terminate_type == CCP_REJECT_INCOMING_CALL &&
                 call_state == CCP_CALL_STATE_INCOMING)
        {
            set_value.val = instance->call_state[idx].tbs_call_id;
        }
        else if (terminate_type == CCP_TERMINATE_HELD_CALLS &&
                 CallControlClient_IsCallHeld(call_state))
        {
            set_value.val = instance->call_state[idx].tbs_call_id;
        }
        else
        {
            continue;
        }

        callControlPoint_TerminateCall(instance, &set_value);
    }
}

CALL_CTRL_CLIENT_KEEP
static voice_source_provider_context_t callControlClient_DeriveVoiceContext(voice_source_provider_context_t voice_context, ccp_call_state_t call_state)
{
    switch (voice_context)
    {
        case context_voice_connected:
            switch (call_state)
            {
                case CCP_CALL_STATE_IDLE:
                    return context_voice_connected;

                case CCP_CALL_STATE_INCOMING:
                    return context_voice_ringing_incoming;

                case CCP_CALL_STATE_OUTGOING_DIALING:
                case CCP_CALL_STATE_OUTGOING_ALERTING:
                    return context_voice_ringing_outgoing;

                case CCP_CALL_STATE_ACTIVE:
                    return context_voice_in_call;

                case CCP_CALL_STATE_LOCALLY_HELD:
                case CCP_CALL_STATE_REMOTELY_HELD:
                case CCP_CALL_STATE_LOCALLY_REMOTELY_HELD:
                    return context_voice_call_held;

                default:
                    break;
            }
            break;

        case context_voice_ringing_incoming:
            switch (call_state)
            {
                case CCP_CALL_STATE_IDLE:
                case CCP_CALL_STATE_INCOMING:
                case CCP_CALL_STATE_OUTGOING_DIALING:
                case CCP_CALL_STATE_OUTGOING_ALERTING:
                case CCP_CALL_STATE_LOCALLY_HELD:
                case CCP_CALL_STATE_REMOTELY_HELD:
                case CCP_CALL_STATE_LOCALLY_REMOTELY_HELD:
                    return context_voice_ringing_incoming;

                case CCP_CALL_STATE_ACTIVE:
                    return context_voice_in_call_with_incoming;

                default:
                    break;
            }
        break;

        case context_voice_ringing_outgoing:
            switch (call_state)
            {
                case CCP_CALL_STATE_IDLE:
                case CCP_CALL_STATE_OUTGOING_DIALING:
                case CCP_CALL_STATE_OUTGOING_ALERTING:
                case CCP_CALL_STATE_LOCALLY_HELD:
                case CCP_CALL_STATE_REMOTELY_HELD:
                case CCP_CALL_STATE_LOCALLY_REMOTELY_HELD:
                    return context_voice_ringing_outgoing;

                case CCP_CALL_STATE_INCOMING:
                    return context_voice_ringing_incoming;

                case CCP_CALL_STATE_ACTIVE:
                    return context_voice_in_call_with_outgoing;

                default:
                    break;
            }
        break;

        case context_voice_in_call:
            switch (call_state)
            {
                case CCP_CALL_STATE_IDLE:
                    return context_voice_in_call;

                case CCP_CALL_STATE_INCOMING:
                    return context_voice_in_call_with_incoming;

                case CCP_CALL_STATE_ACTIVE:
                    return context_voice_in_multiparty_call;

                case CCP_CALL_STATE_OUTGOING_DIALING:
                case CCP_CALL_STATE_OUTGOING_ALERTING:
                    return context_voice_in_call_with_outgoing;

                case CCP_CALL_STATE_LOCALLY_HELD:
                case CCP_CALL_STATE_REMOTELY_HELD:
                case CCP_CALL_STATE_LOCALLY_REMOTELY_HELD:
                    return context_voice_in_call_with_held;

                default:
                    break;
            }
        break;

        case context_voice_call_held:
            switch (call_state)
            {
                case CCP_CALL_STATE_IDLE:
                case CCP_CALL_STATE_LOCALLY_HELD:
                case CCP_CALL_STATE_REMOTELY_HELD:
                case CCP_CALL_STATE_LOCALLY_REMOTELY_HELD:
                    return context_voice_call_held;

                case CCP_CALL_STATE_INCOMING:
                    return context_voice_ringing_incoming;

                case CCP_CALL_STATE_ACTIVE:
                    return context_voice_in_call_with_held;

                case CCP_CALL_STATE_OUTGOING_DIALING:
                case CCP_CALL_STATE_OUTGOING_ALERTING:
                    return context_voice_ringing_outgoing;

                default:
                    break;
            }
        break;

        default:
            break;
    }
    return voice_context;
}

CALL_CTRL_CLIENT_KEEP
unsigned CallClientControl_GetContext(voice_source_t source)
{
    call_control_client_instance_t *instance;
    voice_source_provider_context_t le_voice_context = context_voice_disconnected;

    instance = CallControlClient_GetInstance(call_client_compare_by_voice_source, (unsigned) source);

    if (instance != NULL)
    {
        le_voice_context = context_voice_connected;
        for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
        {
            ccp_call_state_t cur_call_state = CcpSm_GetCallState(instance, idx);
            /* In case of multipoint - sometimes the CCP notification arrives late, in case of twc operation when an
             * active call is put on hold and held call is retrieved, sometimes the notification for the active call
             * which was put on hold - arrives late, the notification for the held call which got retrieved - arrives earlier
             * At this instance there are two active call at same time, which leads the telephony service to terminate
             * unrouted ongoing call, which is bad user experience.
             * To avoid this, check if the pending command for the call is hold, and return the state as locally held.
             * This pending cmd will get clear upon CCP state change arrival.
             */
            if (CcpSm_GetPendingCmdForCall(instance, idx) == TBS_CCP_LOCAL_HOLD)
            {
                cur_call_state = CCP_CALL_STATE_LOCALLY_HELD;
            }

            le_voice_context = callControlClient_DeriveVoiceContext(le_voice_context, cur_call_state);
        }
    }

    CCP_LOG("CallClientControl_GetContext source: enum:voice_source_t:%d, instance:%p, le_voice_context: enum:voice_source_provider_context_t:%d",
            source, instance, le_voice_context);

    return le_voice_context;
}

CALL_CTRL_CLIENT_KEEP
bool CallClientControl_IsCallContextIdle(voice_source_t source)
{
    voice_source_provider_context_t le_voice_context = CallClientControl_GetContext(source);

    return (le_voice_context == context_voice_disconnected || le_voice_context == context_voice_connected);
}

/*! \brief Set the call characteristics notifications */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_SetCallCharacteristicsNotification(gatt_cid_t cid, ccp_notification_id_t type, bool notification_enable)
{
    call_control_client_instance_t *instance;

    instance = CallControlClient_GetInstance(call_client_compare_by_valid_invalid_cid, (unsigned)cid);

    if (instance != NULL)
    {

        CCP_LOG("CallControlClient_SetCallCharacteristicsNotification cid: 0x%x type enum:ccp_notification_id_t:%d", cid, type);

        switch(type)
        {
            case CCP_NOTIFICATION_ID_PROVIDER_NAME:
                CcpSetProviderNameNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_TECHNOLOGY:
                CcpSetTechnologyNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_SIGNAL_STRENGTH:
                CcpSetSignalStrengthNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_LIST_CURRENT_CALL:
                CcpSetListCurrentCallsNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_FLAGS:
                CcpSetFlagsNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_INCOMING_CALL_TARGET_BEARER_URI:
                CcpSetIncomingCallTargetBearerUriNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_CALL_STATE:
                CcpSetCallStateNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_CALL_CONTROL_POINT:
                CcpSetCallControlPointNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_TERMINATION_REASON:
                CcpSetTerminationReasonNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_INCOMING_CALL:
                CcpSetIncomingCallNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            case CCP_NOTIFICATION_ID_CALL_FRIENDLY_NAME:
                CcpSetCallFriendlyNameNotificationRequest(instance->ccp_profile_handle, notification_enable);
            break;

            default : break;
        }
    }
}

/*! \brief Read the call characteristics */
CALL_CTRL_CLIENT_KEEP
bool CallControlClient_ReadCallCharacteristics(gatt_cid_t cid, ccp_characteristics_id_t id)
{
    call_control_client_instance_t *instance;
    bool status = FALSE;

    instance = CallControlClient_GetInstance(call_client_compare_by_valid_invalid_cid, (unsigned)cid);

    if (instance != NULL)
    {
        CCP_LOG("CallControlClient_ReadCallCharacteristics cid: 0x%x type enum:ccp_characteristics_id_t:%d", cid, id);

        status = TRUE;
        switch (id)
        {
            case CCP_CHARACTERISTICS_ID_PROVIDER_NAME:
                CcpReadProviderNameRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_BEARER_UCI:
                CcpReadBearerUciRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_BEARER_TECHNOLOGY:
                CcpReadBearerTechnologyRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_BEARER_URI_SCHEMES_SUPPORTED_LIST:
                CcpReadBearerUriRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_SIGNAL_STRENGTH:
                CcpReadSignalStrengthRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_SIGNAL_STRENGTH_INTERVAL:
                CcpReadSignalStrengthIntervalRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_CURRENT_CALLS_LIST:
                CcpReadCurrentCallsRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_CONTENT_CONTROL_ID:
                CcpReadContentControlIdRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_FEATURE_AND_STATUS_FLAGS:
                CcpReadStatusAndFeatureFlagsRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_INCOMING_CALL_TARGET_BEARER_URI:
                CcpReadIncomingTargetBearerUriRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_CALL_STATE:
                CcpReadCallStateRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_INCOMING_CALL:
                CcpReadIncomingCallRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_CALL_FRIENDLY_NAME:
                CcpReadCallFriendlyNameRequest(instance->ccp_profile_handle);
            break;

            case CCP_CHARACTERISTICS_ID_CCP_OPTIONAL_OPCODES:
                CcpReadCallControlPointOptionalOpcodesRequest(instance->ccp_profile_handle);
            break;

            default:
                status = FALSE;
            break;
        }
    }

    return status;
}

uint16 CallControlClient_ReadCachedCallCharacteristics(gatt_cid_t cid, ccp_characteristics_id_t id, uint8 **pp_data)
{
    uint16 len = 0;
    call_control_client_instance_t *instance;
    uint8 *data = NULL;

    instance = CallControlClient_GetInstance(call_client_compare_by_valid_invalid_cid, (unsigned) cid);

    if (instance != NULL)
    {
        switch (id)
        {
            case CCP_CHARACTERISTICS_ID_FEATURE_AND_STATUS_FLAGS:
                data = malloc(sizeof(uint8));
                if (data != NULL)
                {
                    len = 1;
                    data[0] = (uint8) (instance->tbs_status_info.in_band_ring | (instance->tbs_status_info.silent_mode << 1u));
                }
            break;

            case CCP_CHARACTERISTICS_ID_CALL_STATE:
                data = malloc(CCP_CHARACTERISTICS_CALL_STATE_LEN);
                if (data != NULL)
                {
                    uint8 *write_ptr, *end_ptr, scan;

                    len = CCP_CHARACTERISTICS_CALL_STATE_LEN;
                    for (write_ptr = data, end_ptr = &data[len], scan = 0; write_ptr < end_ptr; write_ptr += 3, scan++)
                    {
                        write_ptr[0] = instance->call_state[scan].tbs_call_id; /* Call_Index */
                        write_ptr[1] = (uint8) instance->call_state[scan].state; /* State */
                        write_ptr[2] = instance->call_state[scan].flags; /* Call_Flags */
                    }
                }
            break;

            case CCP_CHARACTERISTICS_ID_CONTENT_CONTROL_ID:
                data = malloc(sizeof(uint8));
                if (data != NULL)
                {
                    len = 1;
                    data[0] = (uint8) instance->content_control_id;
                }
            break;

            default:
            break;
        }
    }

    *pp_data = data;
    CCP_LOG("CallControlClient_ReadCachedCallCharacteristics cid: 0x%x type enum:ccp_characteristics_id_t:%d, len: %d", cid, id, len);

    return len;
}

bool CallControlClient_WriteCallCharacteristics(gatt_cid_t cid, ccp_characteristics_id_t id, uint16 len, uint8 *value)
{
    call_control_client_instance_t *instance;
    bool status = FALSE;

    instance = CallControlClient_GetInstance(call_client_compare_by_valid_invalid_cid, (unsigned)cid);

    if (instance != NULL)
    {
        CCP_LOG("CallControlClient_WriteCallCharacteristics cid: 0x%x type: enum:ccp_characteristics_id_t:%d len: %u, value: %p",
                cid, id, len, value);

        status = TRUE;
        switch (id)
        {
            case CCP_CHARACTERISTICS_ID_SIGNAL_STRENGTH_INTERVAL:
                CcpWriteSignalStrengthIntervalRequest(instance->ccp_profile_handle, *value, FALSE);
            break;

            case CCP_CHARACTERISTICS_ID_CALL_CONTROL_POINT:
                CcpWriteCallControlPointRequest(instance->ccp_profile_handle, *value, len - 1, &value[1]);
            break;

            default:
                status = FALSE;
            break;
        }
    }

    return status;
}

/*! \brief Send call control point opcode */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_SendCallControlPointOpcode(gatt_cid_t cid, uint8 Opcode, uint8 callid1, uint8 callid2)
{
    call_control_client_instance_t *instance;
    uint8 tbs_call_index[MAX_ACTIVE_CALLS_SUPPORTED] = {0};

    instance = CallControlClient_GetInstance(call_client_compare_by_valid_invalid_cid, (unsigned)cid);

    if (instance != NULL)
    {
        CCP_LOG("CallControlClient_SendCallControlPointOpcode Opcode:0x%x cid: 0x%x", Opcode, instance->cid);

        switch (Opcode)
        {
            case TBS_CCP_ACCEPT:
            case TBS_CCP_TERMINATE :
            case TBS_CCP_LOCAL_HOLD :
            case TBS_CCP_LOCAL_RETRIEVE :
                if (callid1 != 0)
                {
                    CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, Opcode, callid1);
                }
            break;

            case TBS_CCP_JOIN :
                if (callid1 != 0 && callid2 != 0)
                {
                    tbs_call_index[0] = callid1;
                    tbs_call_index[1] = callid2;
                    CcpWriteCallControlPointRequest(instance->ccp_profile_handle, Opcode, 2, tbs_call_index);
                }
            break;

            default : break;
        }
    }
}

/*! \brief Prints all calls info */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_PrintAllCallsInfo(void)
{
    call_control_client_instance_t *call_client = NULL;
    bdaddr addr;

    for (call_client = &call_control_taskdata.call_client_instance[0];
         call_client < &call_control_taskdata.call_client_instance[MAX_CALL_SERVER_SUPPORTED];
         call_client++)
    {
        if (call_client->state == call_client_state_connected)
        {
            GattConnect_GetPublicAddrFromConnectionId(call_client->cid, &addr);
            CCP_LOG("CallControlClient_PrintAllCallsInfo cid: 0x%x, bdaddr: %04x::%02x::%08x",
                    call_client->cid, addr.uap, addr.nap, addr.lap);
            for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
            {
                CCP_LOG("CallControlClient_PrintAllCallsInfo call_id: %d, call_state enum:ccp_call_state_t:%d",
                        call_client->call_state[idx].tbs_call_id, call_client->call_state[idx].state);
            }
        }
    }
}

/*! \brief Release any call which is held/waiting */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_TwcReleaseHeldRejectWaiting(voice_source_t source)
{
    call_control_client_instance_t *instance;

    instance = CallControlClient_GetInstance(call_client_compare_by_voice_source, (unsigned) source);

    if (instance != NULL)
    {
        for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
        {
            ccp_call_state_t call_state = CcpSm_GetCallState(instance, idx);
            if (call_state == CCP_CALL_STATE_LOCALLY_HELD ||
                call_state == CCP_CALL_STATE_REMOTELY_HELD ||
                call_state == CCP_CALL_STATE_LOCALLY_REMOTELY_HELD ||
                call_state == CCP_CALL_STATE_INCOMING)
            {
                /* Release the held / reject the waiting call*/
                CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, TBS_CCP_TERMINATE,
                    instance->call_state[idx].tbs_call_id);
                CCP_LOG("CallControlClient_TwcReleaseHeldRejectWaiting call_id: %d",
                    instance->call_state[idx].tbs_call_id);
            }
        }
    }
}

CALL_CTRL_CLIENT_KEEP
static void CallControlClient_TwcAcceptOther(voice_source_t source, GattTbsOpcode terminate_or_hold)
{
    call_control_client_instance_t *instance;

    instance = CallControlClient_GetInstance(call_client_compare_by_voice_source, (unsigned) source);

    if (instance == NULL)
    {
        return;
    }

    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        ccp_call_state_t call_state = CcpSm_GetCallState(instance, idx);

        if (call_state == CCP_CALL_STATE_ACTIVE)
        {
            /* Terminate/Hold the call in active state */
            CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, terminate_or_hold,
                instance->call_state[idx].tbs_call_id);
            CcpSm_SetPendingCommandForMatchingCallId(instance, instance->call_state[idx].tbs_call_id, terminate_or_hold);

            CCP_LOG("CallControlClient_TwcAcceptOther call_id: %d opcode: %d", instance->call_state[idx].tbs_call_id, terminate_or_hold);
        }
        else if (call_state == CCP_CALL_STATE_INCOMING)
        {
            /* Accept the incoming call */
            CCP_LOG("CallControlClient_TwcAcceptOther accept call: %d", instance->call_state[idx].tbs_call_id);
            CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, TBS_CCP_ACCEPT, instance->call_state[idx].tbs_call_id);
        }
        else if (call_state == CCP_CALL_STATE_LOCALLY_HELD ||
                 call_state == CCP_CALL_STATE_LOCALLY_REMOTELY_HELD)
        {
            /* Retrieve the held call */
            CCP_LOG("CallControlClient_TwcAcceptOther local retrieve call: %d", instance->call_state[idx].tbs_call_id);
            CcpWriteCallControlPointSimpleRequest(instance->ccp_profile_handle, TBS_CCP_LOCAL_RETRIEVE, instance->call_state[idx].tbs_call_id);
        }
    }
}

/*! \brief Release the active call and accept incoming */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_TwcReleaseActiveAcceptOther(voice_source_t source)
{
    CallControlClient_TwcAcceptOther(source, TBS_CCP_TERMINATE);
}

/*! \brief Hold the active call and accept incoming */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_TwcHoldActiveAcceptOther(voice_source_t source)
{
    CallControlClient_TwcAcceptOther(source, TBS_CCP_LOCAL_HOLD);
}

/*! \brief Joins all the calls in call control clients instance */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_TwcJoinCalls(voice_source_t source)
{
    uint8 tbs_call_index[MAX_ACTIVE_CALLS_SUPPORTED] = {0};
    uint8 count = 0;
    call_control_client_instance_t *instance;

    instance = CallControlClient_GetInstance(call_client_compare_by_voice_source, (unsigned) source);
    if (instance == NULL)
    {
        return;
    }

    for (uint8 idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
    {
        ccp_call_state_t call_state = CcpSm_GetCallState(instance, idx);

        if (call_state == CCP_CALL_STATE_ACTIVE ||
            call_state == CCP_CALL_STATE_LOCALLY_HELD ||
            call_state == CCP_CALL_STATE_REMOTELY_HELD ||
            call_state == CCP_CALL_STATE_LOCALLY_REMOTELY_HELD)
        {
            tbs_call_index[count++] = instance->call_state[idx].tbs_call_id;
        }
    }

    if (count > 1)
    {
        CCP_LOG("CallControlClient_TwcJoinCalls join %d calls", count);
        CcpWriteCallControlPointRequest(instance->ccp_profile_handle, TBS_CCP_JOIN, count, tbs_call_index);
    }
}

/*! \brief Get the Call client instance based on the compare type */
CALL_CTRL_CLIENT_KEEP
call_control_client_instance_t * CallControlClient_GetInstance(call_instance_compare_by_type_t type, unsigned cmp_value)
{
    call_control_client_instance_t *instance = NULL;

    for (instance = &call_control_taskdata.call_client_instance[0];
         instance < &call_control_taskdata.call_client_instance[MAX_CALL_SERVER_SUPPORTED];
         instance++)
    {
        if (callControlClient_Compare(type, cmp_value, instance))
        {
            return instance;
        }
    }

    return NULL;
}

/*! \brief Get the stored handles from NVM. It could be either
           a) valid handles if GTBS handles discovered before
           b) Invalid handles if GTBS is not found in remote server
           c) NULL if we have not yet triggered for GTBS handle discovery
 */
CALL_CTRL_CLIENT_KEEP
static void * callControlClient_RetrieveClientHandlesFromNvm(gatt_cid_t cid)
{
    device_t device = GattConnect_GetBtDevice(cid);
    void *server_handle_info = NULL;

    if (device)
    {
        size_t size;

        if (!Device_GetProperty(device, device_property_call_control_client, &server_handle_info, &size))
        {
            server_handle_info = NULL;
        }
    }

    DEBUG_LOG_VERBOSE("callControlClient_RetrieveClientHandlesFromNvm %p", server_handle_info);

    return server_handle_info;
}

CALL_CTRL_CLIENT_KEEP
void * CallControlClient_RetrieveClientHandles(gatt_cid_t cid)
{
    GattTelephoneBearerClientDeviceData *stored_data = callControlClient_RetrieveClientHandlesFromNvm(cid);

    if (stored_data != NULL &&
        (stored_data->startHandle != CALL_CONTROL_CLIENT_INVALID_HANDLE || stored_data->endHandle != CALL_CONTROL_CLIENT_INVALID_HANDLE))
    {
        /* Only return if handles are valid */
        return stored_data;
    }

    return NULL;
}

CALL_CTRL_CLIENT_KEEP
bool CallControlClient_StoreClientHandles(gatt_cid_t cid, void *config, uint8 size)
{
    bool handles_written = FALSE;
    device_t device = GattConnect_GetBtDevice(cid);

    if (device)
    {
        Device_SetProperty(device, device_property_call_control_client, config, size);
        DeviceDbSerialiser_SerialiseDevice(device);
        handles_written = TRUE;
    }

    return handles_written;
}

/*! \brief Reset the provided call client instance */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_ResetCallClientInstance(call_control_client_instance_t *call_client)
{
    if (call_client != NULL)
    {
        memset(call_client, 0, sizeof(call_control_client_instance_t));
        call_client->cid = INVALID_CID;
        for (int idx = 0; idx < MAX_ACTIVE_CALLS_SUPPORTED; idx++)
        {
            call_client->call_state[idx].pending_command = CCP_INVALID_OPCODE;
        }
    }
}

/* Get the content control id for call control client */
CALL_CTRL_CLIENT_KEEP
uint16 CallClientControl_GetContentControlId(gatt_cid_t cid)
{
    call_control_client_instance_t *instance;
    uint16 content_control_id = 0;

    instance = CallControlClient_GetInstance(call_client_compare_by_cid, (unsigned)cid);

    if (instance != NULL)
    {
        content_control_id = instance ->content_control_id;
    }

    return content_control_id;
}

/*! \brief Check If CCP is connected or not */
CALL_CTRL_CLIENT_KEEP
bool CallClientControl_IsCcpConnected(void)
{
    call_control_client_instance_t *instance;

    instance = CallControlClient_GetInstance(call_client_compare_by_valid_invalid_cid, (unsigned)INVALID_CID);

    if (instance != NULL)
    {
        return TRUE;
    }

    return FALSE;
}

/*! \brief Refresh the call state for ccp */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_RefreshCallState(voice_source_t source)
{
    call_control_client_instance_t *instance;
    tp_bdaddr tp_bd_addr = {0};
    gatt_cid_t cid;

    device_t device = CallControlClient_FindDeviceFromVoiceSource(source);
    CCP_LOG("CallControlClient_RefreshCallState device=%p ", device);

    if (device != NULL)
    {
        BtDevice_GetTpBdaddrForDevice(device, &tp_bd_addr);
        cid = GattConnect_GetConnectionIdFromTpaddr(&tp_bd_addr);
        instance = CallControlClient_GetInstance(call_client_compare_by_cid, (unsigned)cid);

        if (instance != NULL)
        {
            CcpReadCallStateRequest(instance->ccp_profile_handle);
        }
    }
}

/*! \brief Find Device from voice source */
CALL_CTRL_CLIENT_KEEP
device_t CallControlClient_FindDeviceFromVoiceSource(voice_source_t source)
{
    return DeviceList_GetFirstDeviceWithPropertyValue(device_property_le_voice_source, &source, sizeof(voice_source_t));
}

/*! \brief Initiate a call using number */
CALL_CTRL_CLIENT_KEEP
void CallControlClient_InitiateCallUsingNumber(voice_source_t source, const char *digits, unsigned number_of_digits)
{
    call_control_client_instance_t *instance;
    const char *prefix = "tel:";
    const char *suffix = ",";
    char *buffer = NULL;
    char *dial_command = NULL;
    unsigned size_dial_command;

    instance = CallControlClient_GetInstance(call_client_compare_by_voice_source, (unsigned) source);
    if (instance == NULL)
    {
        return;
    }

    if (digits == NULL || number_of_digits == 0)
    {
        /* Use fixed number to originate the call */
        digits = CCP_FIXED_NUMBER;
        number_of_digits = CCP_SIZE_OF_FIXED_NUMBER;
    }

    size_dial_command = CCP_SIZE_OF_PREFIX + number_of_digits + CCP_SIZE_OF_SUFFIX;

    /* Create the Originate call command */
    dial_command = (char *) PanicUnlessMalloc(size_dial_command);

    buffer = dial_command;
    memmove(buffer, prefix, CCP_SIZE_OF_PREFIX);
    buffer += CCP_SIZE_OF_PREFIX;
    memmove(buffer, digits, number_of_digits);
    buffer += number_of_digits;
    memmove(buffer, suffix, CCP_SIZE_OF_SUFFIX);

    CCP_LOG("CallControlClient_InitiateCallUsingNumber dial_command size :%d, dial_command : %s", size_dial_command, dial_command);

    /* Send the call control point request*/
    CcpWriteCallControlPointRequest(instance->ccp_profile_handle, TBS_CCP_ORIGINATE, size_dial_command, (uint8*)dial_command);

    /* Free up the allocated memory since the cmd has been copied into the TBSC buffer */
    free(dial_command);
}

/*! \brief Check If CCP is connected or not */
CALL_CTRL_CLIENT_KEEP
bool CallClientControl_IsConnected(gatt_cid_t cid)
{
    return (CallControlClient_GetInstance(call_client_compare_by_cid, (unsigned)cid) != NULL);
}

